iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
Software Development

玩轉ABCL Lisp!用奇怪偏門的方式學習JavaFX系列 第 10 篇

lesson 1: Hello abclFX:參考HelloFX的簡單示例

  • 分享至 

  • xImage
  •  

TL;DR

這篇會把 Java 版的 HelloFX 改寫成 ABCL 版本:先將 JavaFX SDK 的 JAR 檔案載入 ABCL,再用jnew-runtime-class在執行時建立繼承 JavaFX Application 的類別,最後把完整程式存成main.lisp,透過 ABCL 載入執行。

我們會以HelloFX為基準,改成使用ABCL+JavaFX的實現方式。這種實現方式的好處是不需要JDK環境,只需要JRE環境即可。

載入 JavaFX SDK

為了載入JavaFX SDK,需要將jar檔案加入CLASSPATH路徑:

;; add classpath
(ignore-errors
  (let (libs)
    (setf libs
          ;; (make-pathname :name :wild :directory '(:relative "lib") :type "jar")
          (loop for file in (directory "lib/*.jar")
                do
                (setf file (namestring file))
                (print file)
                collect file))
    (add-to-classpath libs)))

建立動態的 HelloFX 類別

你可以像我一樣直接將SDK目錄複製到執行目錄下,然後透過ABCL程式直接將jar檔案遍歷加進CLASSPATH裡。然後需要建立一個動態的JAVA類別:

(let ((HelloFX (jnew-runtime-class "HelloFX"
                                    :superclass "javafx.application.Application"
                                    :methods `(("main" :void ()
                                                       (lambda (this)
                                                         (jstatic "launch" +application+ (jobject-class this) (jnew-array "java.lang.String" 1))))
                                               ("start" :void ("javafx.stage.Stage")
                                                        (lambda (this stage)
                                                          (prog* ((java-version (jstatic "getProperty" "java.lang.System" "java.version"))
                                                                  (javafx-version (jstatic "getProperty" "java.lang.System" "javafx.version"))
                                                                  (l (jnew +label+ (format nil "Hello, JavaFX ~A, running on Java ~A." javafx-version java-version)))
                                                                  (scene (jnew +scene+ (jnew +StackPane+ (jarray-from-list `(,l))) 640 480)))
                                                             (jcall "setScene" stage scene)
                                                             (jcall "show" stage)))))
                                    :access-flags '(:public))))
  (jcall "main" (jnew HelloFX)))

對照 Java 版 HelloFX

上面這一段程式碼可以一行一行對照Java版本的HelloFX。不同的是ABCL的使用不經過javac工具的編譯,因此建立的JAVA類別是屬於運行時建立的runtime-class。

public class HelloFX extends Application 

↑
對應
↓

(jnew-runtime-class "HelloFX" :superclass "javafx.application.Application" :access-flags '(:public)

    public static void main(String[] args) {
        launch();
    }

↑
對應
↓

:methods `(("main" :void ()
                    (lambda (this)
                      (jstatic "launch" +application+ (jobject-class this) (jnew-array "java.lang.String" 1))))

    @Override
    public void start(Stage stage) {
        String javaVersion = System.getProperty("java.version");
        String javafxVersion = System.getProperty("javafx.version");
        Label l = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
        Scene scene = new Scene(new StackPane(l), 640, 480);
        stage.setScene(scene);
        stage.show();
    }

↑
對應
↓

("start" :void ("javafx.stage.Stage")
        (lambda (this stage)
          (prog* ((java-version (jstatic "getProperty" "java.lang.System" "java.version"))
                          (javafx-version (jstatic "getProperty" "java.lang.System" "javafx.version"))
                          (l (jnew +label+ (format nil "Hello, JavaFX ~A, running on Java ~A." javafx-version java-version)))
                          (scene (jnew +scene+ (jnew +StackPane+ (jarray-from-list `(,l))) 640 480)))
              (jcall "setScene" stage scene)
              (jcall "show" stage)))))

不過要是這麼直接執行了,會發生錯誤,因爲還有一些別名常數尚未定義:

(define-symbol-macro +application+ (jclass "javafx.application.Application"))
(define-symbol-macro +scene+ (jclass "javafx.scene.Scene"))
(define-symbol-macro +label+ (jclass "javafx.scene.control.Label"))
(define-symbol-macro +StackPane+ (jclass "javafx.scene.layout.StackPane"))
(define-symbol-macro +stage+ (jclass "javafx.stage.Stage"))

整理成可執行腳本

在定義完成別名常數後,在嘗試建立一次動態的HelloFX類別並執行,就會得到與JAVA版本的相同結果。如果關閉視窗會重新回到REPL的互通介面。遺憾的是JavaFX在同一次執行的環境裡,無法建立多個Application,因此也無法在執行一次。不過可以將輸入的內容儲存成一份腳本程式main.lisp,完整內容如下:

;; add classpath
(ignore-errors
  (let (libs)
    (setf libs
          ;; (make-pathname :name :wild :directory '(:relative "lib") :type "jar")
          (loop for file in (directory "lib/*.jar")
                do
                (setf file (namestring file))
                (print file)
                collect file))
    (add-to-classpath libs)))

(define-symbol-macro +application+ (jclass "javafx.application.Application"))
(define-symbol-macro +scene+ (jclass "javafx.scene.Scene"))
(define-symbol-macro +label+ (jclass "javafx.scene.control.Label"))
(define-symbol-macro +StackPane+ (jclass "javafx.scene.layout.StackPane"))
(define-symbol-macro +stage+ (jclass "javafx.stage.Stage"))



(let ((HelloFX (jnew-runtime-class "HelloFX"
                                    :superclass "javafx.application.Application"
                                    :methods `(("main" :void ()
                                                       (lambda (this)
                                                         (jstatic "launch" +application+ (jobject-class this) (jnew-array "java.lang.String" 1))))
                                               ("start" :void ("javafx.stage.Stage")
                                                        (lambda (this stage)
                                                          (prog* ((java-version (jstatic "getProperty" "java.lang.System" "java.version"))
                                                                  (javafx-version (jstatic "getProperty" "java.lang.System" "javafx.version"))
                                                                  (l (jnew +label+ (format nil "Hello, JavaFX ~A, running on Java ~A." javafx-version java-version)))
                                                                  (scene (jnew +scene+ (jnew +StackPane+ (jarray-from-list `(,l))) 640 480)))
                                                             (jcall "setScene" stage scene)
                                                             (jcall "show" stage)))))
                                    :access-flags '(:public))))
  (jcall "main" (jnew HelloFX)))

這樣當我們想要執行ABCL版本的HelloFX,就可以直接載入:

abcl --load main.lisp --eval '(quit)'

執行結果

執行後應該會看到與 Java 版本相同外觀的 HelloFX 視窗,畫面中央顯示 Java 與 JavaFX 的版本資訊:

HelloFX 執行畫面(ABCL 版本的結果與此相同)

關閉視窗後,程式會回到終端機。這表示 ABCL 已經能在執行時建立 JavaFX 應用程式。接下來程式碼開始變長,下一篇將介紹如何使用 Common Lisp 的 ASDF 工程文件,把程式拆分成多個檔案,讓各部分更容易管理。


上一篇
lesson 0: 程式學習慣例,從HelloFX開始
下一篇
lesson 2: Hello ASDF:CLISP的工程文件「Another System Definition Facility」
系列文
玩轉ABCL Lisp!用奇怪偏門的方式學習JavaFX 共 13 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言